Inside Macintosh: Memory

Previous | Chapter Top | Chapter Contents | Next

Invalid Handles

An invalid handle refers to the wrong area of memory, just as a dangling pointer does. There are three types of invalid handles: empty handles, disposed handles, and fake handles. You must avoid empty, disposed, or fake handles as carefully as dangling pointers. Fortunately, it is generally easier to detect, and thus to avoid, invalid handles.

Disposed Handles

A disposed handle is a handle whose associated relocatable block has been disposed of. When you dispose of a relocatable block (perhaps by calling the procedure DisposeHandle ), the Memory Manager does not change the value of any handle variables that previously referenced that block. Instead, those variables still hold the address of what once was the relocatable block's master pointer. Because the block has been disposed of, however, the contents of the master pointer are no longer defined. (The master pointer might belong to a subsequently allocated relocatable block, or it could become part of a linked list of unused master pointers maintained by the Memory Manager.)

If you accidentally use a handle to a block you have already disposed of, you can obtain unexpected results. In the best cases, your application will crash. In the worst cases, you will get garbled data. It might, however, be difficult to trace the cause of the garbled data, because your application can continue to run for quite a while before the problem begins to manifest itself.

You can avoid these problems quite easily by assigning the value NIL to the handle variable after you dispose of its associated block. By doing so, you indicate that the handle does not point anywhere in particular. If you subsequently attempt to operate on such a block, the Memory Manager will probably generate a nilHandleErr result code. If you want to make certain that a handle is not disposed of before operating on a relocatable block, you can test whether the value of the handle is NIL , as follows:

IF myHandle <> NIL THEN
    ...;                {handle is valid, so we can operate on it here}

This test is useful only if you manually assign the value NIL to all disposed handles. The Memory Manager does not do that automatically.

Empty Handles

An empty handle is a handle whose master pointer has the value NIL . When the Memory Manager purges a relocatable block, for example, it sets the block's master pointer to NIL . The space occupied by the master pointer itself remains allocated, and handles to the purged block continue to point to the master pointer. This is useful, because if you later reallocate space for the block by calling ReallocateHandle , the master pointer will be updated and all existing handles will correctly access the reallocated block.

Don't confuse empty handles with 0-length handles, which are handles whose associated block has a size of 0 bytes. A 0-length handle has a non- NIL master pointer and a block header.

Once again, however, inadvertently using an empty handle can give unexpected results or lead to a system crash. In the Macintosh Operating System, NIL technically refers to memory location 0. But this memory location holds a value. If you doubly dereference an empty handle, you reference whatever data is found at that location, and you could obtain unexpected results that are difficult to trace.

You can check for empty handles much as you check for disposed handles. Assuming you set handles to NIL when you dispose of them, you can use the following code to determine whether a handle both points to a valid master pointer and references a nonempty relocatable block:

IF myHandle <> NIL THEN
    IF myHandle^ <> NIL THEN
        ...             {we can operate on the relocatable block here}

Note that because Pascal evaluates expressions completely, you need two IF - THEN statements rather than one compound statement in case the value of the handle itself is NIL . Most compilers, however, allow you to use "short-circuit" Boolean operators to minimize the evaluation of expressions. For example, if your compiler uses the operator & as a short-circuit operator for AND , you could rewrite the preceding code like this:

IF (myHandle <> NIL) & (myHandle^ <> NIL) THEN
    ...                 {we can operate on the relocatable block here}

In this case, the second expression is evaluated only if the first expression evaluates to TRUE .

The availability and syntax of short-circuit Boolean operators are compiler dependent. Check the documentation for your development system to see whether you can use such operators.

It is useful during debugging to set memory location 0 to an odd number, such as $50FFC001. This causes the Operating System to crash immediately if you attempt to dereference an empty handle. This is useful, because you can immediately fix problems that might otherwise require extensive debugging.

Fake Handles

A fake handle is a handle that was not created by the Memory Manager. Normally, you create handles by either directly or indirectly calling the Memory Manager function NewHandle (or one of its variants, such as NewHandleClear ). You create a fake handle--usually inadvertently--by directly assigning a value to a variable of type Handle , as illustrated in Listing 1-2 .

Listing 2 Creating a fake handle

FUNCTION MakeFakeHandle: Handle;                {DON'T USE THIS FUNCTION!}
CONST
    kMemoryLoc = $100;                          {a random memory location}
VAR
    myHandle:       Handle;
    myPointer:      Ptr;
BEGIN
    myPointer := Ptr(kMemoryLoc);               {the address of some memory}
    myHandle := @myPointer;                     {the address of a pointer}
    MakeFakeHandle := myHandle;
END;

The technique for creating a fake handle shown in Listing 1-2 is included for illustrative purposes only. Your application should never create fake handles.

Remember that a real handle contains the address of a master pointer. The fake handle manufactured by the function MakeFakeHandle in Listing 1-2 contains an address that may or may not be the address of a master pointer. If it isn't the address of a master pointer, then you virtually guarantee chaotic results if you pass the fake handle to a system software routine that expects a real handle.

For example, suppose you pass a fake handle to the MoveHHi procedure. After allocating a new relocatable block high in the heap, MoveHHi is likely to copy the data from the original block to the new block by dereferencing the handle and using, supposedly, a master pointer. Because, however, the value of a fake handle probably isn't the address of a master pointer, MoveHHi copies invalid data. (Actually, it's unlikely that MoveHHi would ever get that far; probably it would run into problems when attempting to determine the size of the original block from the block header.)

Not all fake handles are as easy to spot as those created by the MakeFakeHandle function defined in Listing 1-2 . You might, for instance, attempt to copy the data in an existing record ( myRecord ) into a new handle, as follows:

myHandle := NewHandle(SizeOf(myRecord));                {create a new handle}
myHandle^ := @myRecord;                                 {DON'T DO THIS!}

The second line of code does not make myHandle a handle to the beginning of the myRecord record. Instead, it overwrites the master pointer with the address of that record, making myHandle a fake handle.

Never assign a value directly to a master pointer.

A correct way to create a new handle to some existing data is to make a copy of the data using the PtrToHand function, as follows:

myErr := PtrToHand(@myRecord, myHandle, SizeOf(myRecord));

The Memory Manager provides a set of pointer- and handle-manipulation routines that can help you avoid creating fake handles. See the chapter "Memory Manager" in this book for details on those routines.


© 1997 Apple Computer, Inc.

Previous | Chapter Top | Chapter Contents | Next